﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics.Contracts;
using Konst3d.Efficiency.IO;

// index file:
// <sign:uint><uint:version><int:count><entry:[offset:uint, length:ushort, reserved:ushort]>...

// TOFIX: actually this class should use one of *ListFiles ...

namespace Konst3d.Efficiency.Suite.SparsedBinary
{
    internal class LabelsIndexFile : BinaryFile
    {
        #region Constants

        private const uint SIGNATURE = 0xCDCDCDCD;
        private const uint VERSION = 1;
        private const int INDEX_COUNT_OFFSET = 8;
        private const int INDEX_DATA_OFFSET = 12;
        private const int OFFSET_ENTRY_OFFSET = 0;
        private const int OFFSET_ENTRY_USED = 4;
        private const int OFFSET_ENTRY_RESERVED = 6;
        private const int INDEX_ENTRY_SIZE = 8;

        #endregion // Constants

        #region Types

        internal class IndexEntry
        {
            public uint Offset;
            public ushort Used;
            public ushort Reserved;
        }

        #endregion // Types

        #region Create/Load Methods

        public static LabelsIndexFile Create(string path, bool overwrite)
        {
            Contract.Requires(!String.IsNullOrEmpty(path));

            if (!overwrite && File.Exists(path))
            {
                throw new IOException(String.Format("File is already exists {0}", path));
            }

            FileStream stream = null;
            BinaryReader reader = null;
            BinaryWriter writer = null;

            try
            {
                stream = File.Open(path, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
                reader = new BinaryReader(stream, Encoding.UTF8);
                writer = new BinaryWriter(stream, Encoding.UTF8);

                writer.Write(SIGNATURE);
                writer.Write(VERSION);
                writer.Write((int)0);

                writer.Flush();
                writer.Seek(0, SeekOrigin.Begin);

                return new LabelsIndexFile(stream, reader, writer);
            }
            catch
            {
                if (stream != null)
                {
                    stream.Dispose();
                }

                if (reader != null)
                {
                    reader.Dispose();
                }

                if (writer != null)
                {
                    writer.Dispose();
                }

                throw;
            }
        }

        public static LabelsIndexFile Open(string path)
        {
            Contract.Requires(!String.IsNullOrEmpty(path));

            if (!File.Exists(path))
            {
                throw new IOException(String.Format("Failed to locate labels file {0}.", path));
            }

            FileStream stream = null;
            BinaryReader reader = null;
            BinaryWriter writer = null;

            try
            {
                stream = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
                reader = new BinaryReader(stream, Encoding.UTF8);
                writer = new BinaryWriter(stream, Encoding.UTF8);

                return new LabelsIndexFile(stream, reader, writer);
            }
            catch
            {
                if (stream != null)
                {
                    stream.Dispose();
                }

                if (reader != null)
                {
                    reader.Dispose();
                }

                if (writer != null)
                {
                    writer.Dispose();
                }

                throw;
            }
        }

        #endregion // Create/Load Methods

        #region Constructor

        public LabelsIndexFile(FileStream stream, BinaryReader reader, BinaryWriter writer):
            base(stream, reader, writer, SIGNATURE, VERSION)
        {
            #region Read Indices

            int indexCount = _reader.ReadInt32();

            for (int i = 0; i < indexCount; ++i)
            {
                var entry = new IndexEntry();

                entry.Offset = _reader.ReadUInt32();
                entry.Used = _reader.ReadUInt16();
                entry.Reserved = _reader.ReadUInt16();

                _entries.Add(entry);

                if (entry.Used == 0)
                {
                    _empty.Add(i);
                }
            }

            #endregion // Read Indices
        }

        #endregion // Constructor

        #region Public Methods

        public int AddEntry(uint offset, ushort size, ushort reserved)
        {
            Contract.Requires(reserved >= size);

            _writer.Write(offset, 0, SeekOrigin.End);
            _writer.Write(size);
            _writer.Write(reserved);

            _entries.Add(new IndexEntry() { Offset = offset, Used = size, Reserved = reserved });

            _writer.Write(_entries.Count, INDEX_COUNT_OFFSET);
            _writer.Flush();

            return _entries.Count - 1;
        }

        public IndexEntry GetEntry(int index)
        {
            Contract.Requires(index >= 0);
            Contract.Assert(index < _entries.Count);

            return _entries[index];
        }

        public int FindEmptyEntry(int size)
        {
            ushort slotSize = ushort.MaxValue;
            int result = -1;

            foreach (int index in _empty)
            {
                var entry = _entries[index];

                if (entry.Reserved == size)
                {
                    result = index;
                    break;
                }

                if (entry.Reserved > size && entry.Reserved < slotSize)
                {
                    slotSize = entry.Reserved;
                    result = index;
                }
            }

            return result;
        }

        public void UpdateEntry(int id, ushort size)
        {
            Contract.Requires(id >= 0);
            Contract.Assert(id < _entries.Count);

            var entry = _entries[id];

            if (size > 0)
            {
                if (entry.Used == 0)
                {
                    _empty.Remove(id);
                }
            }
            else
            {
                if (entry.Used != 0)
                {
                    _empty.Add(id);
                }
            }

            _writer.Write(size, INDEX_DATA_OFFSET + id * INDEX_ENTRY_SIZE + OFFSET_ENTRY_USED);
            _writer.Flush();

            entry.Used = size;
        }

        #endregion // Public Methods

        #region IDisposable Members

        protected override void DisposeInternal(bool disposing)
        {
            base.DisposeInternal(disposing);

            if (disposing)
            {
                _entries.Clear();
                _entries = null;

                _empty.Clear();
                _empty = null;
            }
        }

        #endregion // IDisposable Members

        #region Fields

        private List<IndexEntry> _entries = new List<IndexEntry>();
        private List<int> _empty = new List<int>();

        #endregion // Fields
    }
}
